from __future__ import print_function
from threading import Thread

def trykey(o,k,d):
	try:return o[k]
	except KeyError:return d

class Runner:
	def __init__(self,task):self.task=task
	def __call__(self):
		t=self.task
		t.process_signal('start')
		while t.running:
			try:t.process_signal('frame')
			except Exception as e:
				print("Exception occured in",type(t).__name__)
				raise e

class EventHandler:

	class Signal:
		def __init__(self,sig):self.signal=sig
		def __call__(self,func):
			def wrap(obj,*args,**kwargs):
				obj.process_signal(self.signal,*args,**kwargs)
				func(obj,*args,**kwargs)
				obj.process_signal('post-'+self.signal,*args,**kwargs)
			return wrap

	def __init__(self):self.events={}

	@property
	def running(self):return True

	@Signal('start')
	def start(self):pass

	@Signal('stop')
	def stop(self):pass

	def connect_signal(self,event,*handler):
		if event not in self.events:
			self.events[event]=[h for h in handler]
		else:self.events[event].extend([h for h in handler if h not in self.events[event]])

	def disconnect_signal(self,*handlers,event=None):
		if event:
			for handler in handlers:
				if handler in self.events[event]:
						self.events[event].remove(handler)
		else:
			for listener in self.events.values():
				for handler in handlers:
					if handler in listener:listener.remove(handler)

	def process_signal(self,event,*args,**kwargs):
		for handler in trykey(self.events,event,[]):handler(*args,**kwargs)

class Task(EventHandler):

	def __init__(self,engine,name=None):
		EventHandler.__init__(self)
		self.engine=engine

	@property
	def running(self):return self in self.engine.tasks

	@EventHandler.Signal('frame')
	def frame(self):pass

	def run(self):
		try:self.frame()
		except Exception as e:
			print("Exception occured in",type(self).__name__)
			raise e

	def stop(self):
		EventHandler.stop(self)
		self.engine.stop_task(self)

	def start(self):
		EventHandler.start(self)
		self.engine.start_task(self)

class ThreadedTask(Task):

	def __init__(self,engine,daemon=False):
		Task.__init__(self,engine)
		self.daemon=daemon

	def run(self):pass#Engines will call this method. Taskrunner calls frame directly.

	def start(self):
		Task.start(self)
		nname="{}::{}".format(*[type(o).__name__ for o in [self.engine,self]])
		t=Thread(name=nname,target=Runner(self))
		#t.daemon=self.daemon
		t.start()

class TaskDelegate(EventHandler):

	def __init__(self):
		EventHandler.__init__(self)
		self.tasks=[]

	def kill_tasks(self):
		for t in self.tasks[:]:t.stop()
		self.tasks=[]

	def sub_tasks(self):
		for task in self.tasks[:]:task.run()

	def start_task(self,task):
		self.process_signal('start-task',task)
		if task not in self.tasks:
			self.tasks.append(task)

	def stop_task(self,task):
		self.process_signal('stop-task',task)
		if task in self.tasks:
			self.tasks.remove(task)

	def frame(self):
		Task.frame(self)
		self.sub_tasks()

	@property
	def running(self):return len(self.tasks)>0

class Engine(TaskDelegate):

	def __init__(self):
		TaskDelegate.__init__(self)
		self.connect_signal('frame',self.sub_tasks)
		self.connect_signal('stop',self.kill_tasks)

if __name__=="__main__":
	from time import clock as ticks
	from threading import Thread

	class Timer:

		def __init__(self,taskdel):
			self.timers={}
			taskdel.connect_signal('start-task',self.set_timer)
			taskdel.connect_signal('stop-task',self.check_timer)

		def set_timer(self,task):
			self.timers[task]=ticks()

		def check_timer(self,task):
			print(type(task).__name__,"took",ticks()-self.timers[task],"seconds.")

	class Wait(ThreadedTask):
	
		def __init__(self,engine,time):
			ThreadedTask.__init__(self,engine)
			self.connect_signal('frame',self.check)
			self.connect_signal('start',self.reset_time)
			self.destination,self.time=time,0
		def reset_time(self):self.time=ticks()+self.destination
		def check(self):self.stop() if ticks()>=self.time else None

	class PalindromeSolver(ThreadedTask):

		def __init__(self,engine,num_digits,bottom):
			Task.__init__(self,engine)
			self.bottom=bottom-1
			self.largest=0
			self.top=10**num_digits
			self.num_digits=num_digits
			self.left,self.right=self.top,self.top
			self.connect_signal('frame',self.check)
			self.connect_signal('frame',self.update)
			self.connect_signal('stop',self.output_largest)

		def check(self):
			def half(l,s):return l[len(l)//2:] if s else l[:len(l)//2]
			def is_palindrome(l):return half(l,False)==half(l,True)[::-1]
			v=self.left*self.right
			if v%10 and v>self.largest:
				if is_palindrome(str(v)):
					self.largest=v

		def update(self):
			self.right-=1
			if self.right<=self.bottom:
				self.left-=1
				if self.left<=self.bottom:
					self.stop()
					return
				self.right=self.top

		def output_largest(self):
			print("The largest palindrome formed by the product of two,",self.num_digits,"digit numbers is",self.largest)

	class EulerSeven(Engine):

		def __init__(self):
			Engine.__init__(self)
			self.connect_signal('start',self.initial_tasks)

		def initial_tasks(self):
			PalindromeSolver(self,3,100).start()

	class TimedEulerSeven(Engine):

		def __init__(self):
			Engine.__init__(self)
			Timer(self)
			self.connect_signal('start',PalindromeSolver(self,3,100).start)
			self.connect_signal('start',Wait(self,2).start)

	Thread(target=Runner(EulerSeven())).start()
	Thread(target=Runner(TimedEulerSeven())).start()